package com.dao;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Date;

import com.audit.AuditOutput;
import com.audit.annotation.AuditDate;
import com.audit.annotation.AuditEntity;
import com.audit.annotation.AuditSource;
import com.audit.annotation.AuditUser;


public abstract class AbstractAuditDao extends AbstractDao {

    @Override
    public void save(Object obj) {
        // TODO AUDIT OBJECT
        super.save(obj);

        Object auditObject = audit(obj);
        log(auditObject);
    }

    @Override
    public void delete(Object obj) {
        // TODO AUDIT OBJECT
        super.delete(obj);
    }

    private Object audit(Object obj) {
        return createAuditObject(obj);

    }

    private void log(Object object) {
        if (object instanceof AuditOutput) {
            String output = ((AuditOutput) object).output();
            System.out.println(output);
        }
    }

    private Object createAuditObject(Object entity) {

        try {

            Object auditEntity = getAuditEntity(entity);

            fillAuditEntity(entity, auditEntity);

            return auditEntity;
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

    protected void fillAuditEntity(Object entity, Object auditEntity) throws Exception {
        initGeneralFields(entity, auditEntity);
        initSpecialFields(auditEntity);
    }

    private void initGeneralFields(Object entity, Object auditEntity) throws Exception {
        Method[] entityMethods = entity.getClass().getMethods();
        for (Method entityMethodItem : entityMethods) {
            AuditSource auditSource = entityMethodItem.getAnnotation(AuditSource.class);
            if (auditSource != null) {
                Field auditField = auditEntity.getClass().getDeclaredField(auditSource.destination());
                auditField.setAccessible(true);
                Object value = entityMethodItem.invoke(entity);
                if (value == null) {
                    if (Boolean.parseBoolean(auditSource.nullable())) {
                        auditField.set(auditEntity, null);
                    } else {
                        initDefaultValue(auditEntity, auditField);
                    }
                } else {
                    auditField.set(auditEntity, value);
                }
            }
        }
    }

    private void initDefaultValue(Object auditEntity, Field auditField) throws Exception {
        Type fieldType = auditField.getGenericType();
        String className = ((Class) fieldType).getName();
        Object newValue = getDefaultValue(className);
        auditField.set(auditEntity, newValue);
    }

    private Object getDefaultValue(String className) throws Exception {

        Class<?> Clazz = Class.forName(className);

        if (Clazz.isInstance(new String())) {
            return new String();
        } else if (Clazz.isInstance(new Integer(0))) {
            return new Integer(0);
        } else if (Clazz.isInstance(new Float(0))) {
            return new Float(0);
        } else if (Clazz.isInstance(new Double(0))) {
            return new Double(0);
        } else if (Clazz.isInstance(new Long(0))) {
            return new Long(0);
        } else if (Clazz.isInstance(new Boolean(false))) {
            return new Boolean(false);
        } else if (Clazz.isInstance(new Byte("0"))) {
            return new Byte("0");
        } else if (Clazz.isInstance(new Short("0"))) {
            return new Short("0");
        } else {
            // TODO: change exception type
            throw new Exception("Audit doesn't support the type + " + className);
        }
    }

    //Boolean.parseBoolean(auditSource.nullable())
    private void initSpecialFields(Object auditEntity) throws IllegalAccessException {
        Field[] fields = auditEntity.getClass().getDeclaredFields();
        for (Field fieldItem : fields) {
            Annotation[] annotations = fieldItem.getDeclaredAnnotations();
            if (annotations.length > 0) {
                for (Annotation annotationItem : annotations) {
                    if (annotationItem.annotationType().equals(AuditDate.class)) {
                        initField(auditEntity, fieldItem, new Date());
                    } else if (annotationItem.annotationType().equals(AuditUser.class)) {
                        // TODO: add get current user's id functionality instead "new Long(202)"
                        initField(auditEntity, fieldItem, new Long(202));
                    }
                }
            }
        }
    }

    private void initField(Object auditEntity, Field field, Object value) throws IllegalAccessException {
        field.setAccessible(true);
        field.set(auditEntity, value);
    }

    private Object getAuditEntity(Object entity)
            throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException,
            ClassNotFoundException {

        Class className = entity.getClass().getAnnotation(AuditEntity.class).className();
        Class<?> auditClass = Class.forName(className.getName());
        Constructor auditEntityConstructor = auditClass.getDeclaredConstructor();

        return auditEntityConstructor.newInstance();
    }


}
